/**
Copyright (c) 2013 cnova

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

library SpringCore initializer INIT requires SpringHandles, SpringBase, SpringTable, optional SpringUnitIndexer

    globals 
        integer Spring_Current_Instance

        private force DUMMY_FORCE

        private integer KEY_ARRAY_SIZE
        
        private integer KEY_INSTANCE_TO_INSTANCE_ID
        private integer KEY_INSTANCE_TO_INSTANCE_ENABLED
        private integer KEY_INSTANCE_TO_INSTANCE_OBJECT_ID
        
        private integer KEY_UNIT_REINCARNATING
        private integer KEY_UNIT_DEAD
        
        private integer KEY_UNIT_TYPE_ID_FILTERS
        private integer KEY_UNIT_NAME_FILTERS
        private integer KEY_NULL_FILTERS
        
        private timer Spring_Timer_1
        
        private boolexpr array initFunctions
        private integer initFunctions_N = 0
        
        private unit array Reincarnating
        private integer Reincarnating_N = 0

        private rect MAP_RECT
		
        constant integer EVENT_CHANGE_OWNER  = GetHandleId(EVENT_PLAYER_UNIT_CHANGE_OWNER)
        constant integer EVENT_LEVEL   = GetHandleId(EVENT_PLAYER_HERO_LEVEL)
        constant integer EVENT_UNIT_DIE   = GetHandleId(EVENT_PLAYER_UNIT_DEATH)
        
        constant integer EVENT_SPELL_LEARN   = GetHandleId(EVENT_PLAYER_HERO_SKILL)
        constant integer EVENT_SPELL_EFFECT = GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)
        constant integer EVENT_SPELL_CHANNEL = GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)
        constant integer EVENT_SPELL_CAST = GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)
        constant integer EVENT_SPELL_ENDCAST = GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)
        constant integer EVENT_SPELL_FINISH = GetHandleId(EVENT_PLAYER_UNIT_SPELL_FINISH)
        
        constant integer EVENT_ISSUED_ORDER = GetHandleId(EVENT_PLAYER_UNIT_ISSUED_ORDER)
        constant integer EVENT_ISSUED_POINT_ORDER = GetHandleId(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        constant integer EVENT_ISSUED_TARGET_ORDER = GetHandleId(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        constant integer EVENT_ISSUED_UNIT_ORDER = GetHandleId(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER)
        
        constant integer EVENT_ATTACKED = GetHandleId(EVENT_PLAYER_UNIT_ATTACKED)
        constant integer EVENT_ATTACK = GetHandleId(Location(0,0))
        
        constant integer EVENT_DAMAGED = GetHandleId(EVENT_UNIT_DAMAGED)
        constant integer EVENT_DAMAGE = GetHandleId(Location(0,0))
        
        constant integer EVENT_REINCARNATION_START = GetHandleId(Location(0,0))
        constant integer EVENT_REINCARNATION_FINISH = GetHandleId(Location(0,0))
        
        constant integer EVENT_RESSURECT = GetHandleId(Location(0,0))
        constant integer EVENT_ENTER_MAP = GetHandleId(Location(0,0))
        constant integer EVENT_ENTER_PRE_ENTER_MAP = GetHandleId(Location(0,0))
		
		private integer array SPRING_ALL_EVENT_ARRAY
        private integer SPRING_ALL_EVENT_ARRAY_LENGTH
    endglobals
    
	// spring external api
	struct Spring extends array
		static method getBean takes unit u, string beanName returns integer 
			return LoadInteger(Spring_main_table, -GetHandleId(u), StringHash(beanName))
		endmethod
	endstruct
	// end external api
	
	
	    // protected functions, not to be used outside spring
	function Spring_Is_Bean_Enabled takes integer instance_id returns boolean
		return LoadBoolean(Spring_main_table, instance_id, KEY_INSTANCE_TO_INSTANCE_ENABLED)       
	endfunction
	function Spring_Set_Bean_Enabled takes integer instance_id, boolean e returns nothing
		call SaveBoolean(Spring_main_table, instance_id, KEY_INSTANCE_TO_INSTANCE_ENABLED, e)       
	endfunction

    function Spring_Add_On_Init takes boolexpr E returns nothing
        set initFunctions[initFunctions_N] = E
        set initFunctions_N = initFunctions_N + 1
    endfunction
    function Spring_AddUnitNameFilter takes boolexpr e, string name returns nothing
        local integer arr 
        local integer arr_size
        local integer stringhash = StringHash(name)
        local hashtable ht = Spring_main_table

        if not HaveSavedInteger(ht, KEY_UNIT_NAME_FILTERS, stringhash) then
            set arr = CreateHandle()
            set arr_size = 0
            call SaveInteger(ht, KEY_UNIT_NAME_FILTERS, stringhash, arr)
        else
            set arr = LoadInteger(ht, KEY_UNIT_NAME_FILTERS, stringhash)
            set arr_size = LoadInteger(ht, arr, KEY_ARRAY_SIZE)
        endif
        call SaveBooleanExprHandle(ht, arr, arr_size, e)
        call SaveInteger(ht, arr, KEY_ARRAY_SIZE, arr_size + 1)
        set ht = null
    endfunction
    function Spring_AddUnitTypeIdFilter takes boolexpr e, integer typeId returns nothing
        local integer arr 
        local integer arr_size
        local hashtable ht = Spring_main_table

        if not HaveSavedInteger(ht, KEY_UNIT_TYPE_ID_FILTERS, typeId) then
            set arr = CreateHandle()
            set arr_size = 0
            call SaveInteger(ht, KEY_UNIT_TYPE_ID_FILTERS, typeId, arr)
        else
            set arr = LoadInteger(ht, KEY_UNIT_TYPE_ID_FILTERS, typeId)
            set arr_size = LoadInteger(ht, arr, KEY_ARRAY_SIZE)
        endif
        call SaveBooleanExprHandle(ht, arr, arr_size, e)
        call SaveInteger(ht, arr, KEY_ARRAY_SIZE, arr_size + 1)
        set ht = null
    endfunction
    function Spring_AddNullFilter takes boolexpr e returns nothing
        local integer arr 
        local integer arr_size
        local hashtable ht = Spring_main_table

        if not HaveSavedInteger(ht, KEY_NULL_FILTERS, 0) then
            set arr = CreateHandle()
            set arr_size = 0
            call SaveInteger(ht, KEY_NULL_FILTERS, 0, arr)
        else
            set arr = LoadInteger(ht, KEY_NULL_FILTERS, 0)
            set arr_size = LoadInteger(ht, arr, KEY_ARRAY_SIZE)
        endif
        call SaveBooleanExprHandle(ht, arr, arr_size, e)
        call SaveInteger(ht, arr, KEY_ARRAY_SIZE, arr_size + 1)
        set ht = null
    endfunction
    function Spring_AddObjectIdFilter takes integer instance_id, integer object_id returns nothing
        call SaveInteger(Spring_main_table, instance_id, KEY_INSTANCE_TO_INSTANCE_OBJECT_ID, object_id)        
    endfunction
    function Spring_Destroy_Handle takes integer h returns nothing
        call FlushChildHashtable(Spring_main_table, h)
        call ReuseHandle(h)
    endfunction
    function Spring_Create_Handle takes nothing returns integer
        return CreateHandle()
    endfunction
    function Spring_Save_Handle takes integer instance, integer instance_id, integer e, integer u, boolexpr expr returns nothing
        local integer arr 
        local integer arr_size
        local hashtable ht = Spring_main_table

        if not HaveSavedInteger(ht, u, e) then
            set arr = CreateHandle()
            set arr_size = 0
            call SaveInteger(ht, u, e, arr)
        else
            set arr = LoadInteger(ht, u, e)
            set arr_size = LoadInteger(ht, arr, KEY_ARRAY_SIZE)
        endif

        call SaveInteger(ht, arr, arr_size, instance_id)
        call SaveInteger(ht, arr, KEY_ARRAY_SIZE, arr_size + 1)
        
        // the below should not be recycled unless the instance itself is destroyed
        call SaveBoolean(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ENABLED, true)
        call SaveInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID, instance)
        call SaveBooleanExprHandle(ht, instance_id, e, expr)  
        set ht = null
    endfunction
	function Spring_Save_Bean_Context takes integer u, string beanName, integer instance returns nothing
		// whooptidy doo, all integer handles are positive, so we can double the namespace!
		call SaveInteger(Spring_main_table, -u, StringHash(beanName), instance)
	endfunction
	// -- end protected functions
	
	
    // ==============================

    // create attack detect code if exists
    static if SPRING_ATTACK_DETECT then
    //-----------------------------------------------------
    private function AttackEventEventHandler takes nothing returns boolean
        local force f = SPRING_DUMMY_FORCE
        local integer u = GetHandleId(GetTriggerUnit())
        local integer e = EVENT_ATTACKED
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        local hashtable ht = Spring_main_table

        // moved to top for miniscule performance gain
        // set u = GetHandleId(GetTriggerUnit())
        // set e = EVENT_ATTACKED
        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
        
        set u = GetHandleId(GetAttacker())
        set e = EVENT_ATTACK
        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif

        set f = null
        set ht = null
        return false
    endfunction
    //-----------------------------------------------------
    else
    // attack detect is set to false
    //-----------------------------------------------------
    endif

    // create damage detect code if exists
    //-----------------------------------------------------
    static if SPRING_DAMAGE_DETECT then
    //-----------------------------------------------------
    globals
        private trigger DDCurrentTrigger = null
        private trigger DDPreviousTrigger = null
        private group DDSwapGroup 
    endglobals
    private function DamageEventHandler takes nothing returns boolean
        local force f = SPRING_DUMMY_FORCE
        local integer u
        local integer e
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        local hashtable ht = Spring_main_table

        set u = GetHandleId(GetTriggerUnit())
        set e = EVENT_DAMAGED
        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
        
        set u = GetHandleId(GetEventDamageSource())
        set e = EVENT_DAMAGE
        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
        set f = null
        set ht = null
        return false
    endfunction
    private function DDUnitFilter takes nothing returns boolean
        call TriggerRegisterUnitEvent(DDCurrentTrigger, GetFilterUnit(), EVENT_UNIT_DAMAGED)
        return true
    endfunction
    private function DDSwapTrigger takes nothing returns nothing
            local integer i = 0
            local boolean b = IsTriggerEnabled(DDCurrentTrigger)
                
            call DisableTrigger(DDCurrentTrigger)
            if DDPreviousTrigger != null then
                call DestroyTrigger(DDPreviousTrigger)
            endif
            set DDPreviousTrigger = DDCurrentTrigger
            set DDCurrentTrigger = CreateTrigger()
            call TriggerAddCondition(DDCurrentTrigger, Condition (function DamageEventHandler))
                 
            if not(b) then
                call DisableTrigger(DDCurrentTrigger)
            endif
                
            call GroupEnumUnitsInRect(DDSwapGroup, MAP_RECT, Condition(function DDUnitFilter))
    endfunction
    //-----------------------------------------------------
    else
    // damage detect is set to false
    //-----------------------------------------------------
    endif

    private function SpellEventHandler takes nothing returns boolean
        local integer e = GetHandleId(GetTriggerEventId())
        local integer u = GetHandleId(GetTriggerUnit())
        local force f = SPRING_DUMMY_FORCE
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        local hashtable ht = Spring_main_table
        local integer objid
        local integer aid

        if HaveSavedInteger(ht, u, e) then

            if e == EVENT_SPELL_LEARN then
                set aid = GetLearnedSkill()
            elseif e == EVENT_SPELL_EFFECT or e == EVENT_SPELL_CHANNEL or e == EVENT_SPELL_CAST or e == EVENT_SPELL_ENDCAST or e == EVENT_SPELL_FINISH then 
                set aid = GetSpellAbilityId()
            else
                set aid = GetIssuedOrderId()
            endif
 
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
			
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                if LoadBoolean(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ENABLED) then
                    set objid = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_OBJECT_ID)
                    if objid < 0 or objid == aid then
                        set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                        call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                    endif
                endif
                set i = i - 1
            endloop
        endif
        set f = null
        set ht = null
        return false
    endfunction

    private function MapEnterEventHandler takes nothing returns boolean
        local integer e 
        local integer u = GetHandleId(GetFilterUnit())
        local force f = SPRING_DUMMY_FORCE
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        local hashtable ht = Spring_main_table

		set e = EVENT_ENTER_PRE_ENTER_MAP
        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
		
		set e = EVENT_ENTER_MAP
        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
		
        set f = null
        set ht = null
        return false
    endfunction
    
    private function ReincarnationStartEventHandler takes nothing returns nothing
        local integer e = EVENT_REINCARNATION_START
        local integer u = 0
        local integer n = Reincarnating_N
        local unit U
        local force f = SPRING_DUMMY_FORCE
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        
        loop
            exitwhen n < 0
            set U = Reincarnating[n]
            set u = GetHandleId(U)
            if GetUnitTypeId(U) != 0 and LoadBoolean(Spring_main_table, u, KEY_UNIT_REINCARNATING) then
                // reincarnated
                if HaveSavedInteger(Spring_main_table, u, e) then
                    set arr = LoadInteger(Spring_main_table, u, e)
                    set i = LoadInteger(Spring_main_table, arr, KEY_ARRAY_SIZE) - 1
                    loop
                        exitwhen 0 > i
                        set instance_id = LoadInteger(Spring_main_table, arr, i)
                        set Spring_Current_Instance = LoadInteger(Spring_main_table, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                        call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(Spring_main_table, instance_id, e), 1)
                        set i = i - 1
                    endloop
                endif
            endif
            set Reincarnating[n] = null
            set n = n - 1
        endloop
        set Reincarnating_N = -1
        set U = null
        set f = null
    endfunction
    
    private function ReincarnationFinishEventHandler takes nothing returns nothing
        local integer e = EVENT_REINCARNATION_FINISH
        local integer u = GetHandleId(GetTriggerUnit())
        local force f = SPRING_DUMMY_FORCE
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        local hashtable ht = Spring_main_table

        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
        set f = null
        set ht = null
    endfunction
    
    private function StateChangeEventHandler takes nothing returns boolean
        local integer e = GetHandleId(GetTriggerEventId())
        local integer u = GetHandleId(GetTriggerUnit())
        local force f = SPRING_DUMMY_FORCE
        local integer arr 
        local integer i 
        local integer instance
        local integer instance_id
        local hashtable ht = Spring_main_table

        if HaveSavedInteger(ht, u, e) then
            set arr = LoadInteger(ht, u, e)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                set instance_id = LoadInteger(ht, arr, i)
                set Spring_Current_Instance = LoadInteger(ht, instance_id, KEY_INSTANCE_TO_INSTANCE_ID)
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, instance_id, e), 1)
                set i = i - 1
            endloop
        endif
        set f = null
        set ht = null
        if ( e == GetHandleId(EVENT_PLAYER_UNIT_DEATH)) then
            // death event will not fire for reincarnating units because the engine treats them as alive
            // so we tell our sytem the unit is truly dead and cancel the check
            // this event fires AFTER the unit is ordered undefend, potentially detecting a "death" before this is fired
            call SaveBoolean(Spring_main_table, u, KEY_UNIT_REINCARNATING, false)
        endif   
        return false
    endfunction


    //==============================================
    // spring initialization 
    //----------------------------------------
    
    // leave detection
    private function DestroyUnitStructsOnExit takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer uid = GetHandleId(u)
        local integer i 
        local integer arr
        local hashtable ht = Spring_main_table

        if GetUnitAbilityLevel(u, SPRING_ABILITIES_UNIT_INDEXER) == 0 then
            // TODO INVOKE UNIT LEFT MAP EVENT

            // unit disappeared from the map completely, clean up everything allocated
            set i = SPRING_ALL_EVENT_ARRAY_LENGTH - 1
            loop
                exitwhen 0 > i
                if HaveSavedInteger(ht, uid, SPRING_ALL_EVENT_ARRAY[i]) then
                    set arr = LoadInteger(ht, uid, SPRING_ALL_EVENT_ARRAY[i])
                    // inlinging Spring_Destroy_Handle
                    call FlushChildHashtable(ht, arr)
                    call ReuseHandle(arr)
                endif
                set i = i - 1
            endloop

            // unit exited the map, that handle is gone forever
            // so we can reuse it even if technically we did not allocate it
            // inlining Spring_Destroy_Handle
			call FlushChildHashtable(ht, -uid)
            call FlushChildHashtable(ht, uid)
            call ReuseHandle(uid)
			
			static if SPRING_UNIT_INDEXER then
				call Spring_destroyUnitId(u)
			endif
        else
            // unit was alive, and but now is "dead", 
            // but ON_DEATH event may still not fire if the unit is going to reincarnate 
            if IsUnitType(u, UNIT_TYPE_DEAD) and not LoadBoolean(ht, uid, KEY_UNIT_DEAD) then 
                // Assume any unit that "dies" is going to reincarnate
                // this assumption is safe because if the unit truly died, it will fire a death event
                // which will be intercepted in the next method "ReincarnationStartEventHandler"
                call SaveBoolean(ht, uid, KEY_UNIT_DEAD, true) 
                call SaveBoolean(ht, uid, KEY_UNIT_REINCARNATING, true) 
                    
                set Reincarnating_N = Reincarnating_N + 1 //Add the dying unit to a stack and 
                set Reincarnating[Reincarnating_N] = u    //check the flag 0. seconds later.
                call TimerStart(Spring_Timer_1, 0.01, false, function ReincarnationStartEventHandler)

            // unit is alive now but was flagged as dead, therefore, unit has ressurected
            elseif LoadBoolean(ht, uid, KEY_UNIT_DEAD) then
                call SaveBoolean(ht,  uid, KEY_UNIT_DEAD, false) 
                if LoadBoolean(ht, uid, KEY_UNIT_REINCARNATING ) then
                    call SaveBoolean(ht, uid, KEY_UNIT_REINCARNATING , false)
                    call ReincarnationFinishEventHandler()
                endif
            endif
        endif
        set ht = null
        set u = null
        return true
    endfunction
    
    // enter detection
    private function InitUnitStructsOnEnter takes nothing returns boolean
        local unit u = GetFilterUnit()
        local integer uid = GetHandleId(u)
        local force f = SPRING_DUMMY_FORCE
        local integer i 
        local integer arr
        local integer namehash = StringHash(GetUnitName(u))
        local integer typeId = GetUnitTypeId(u)
        local boolean valid = false
        local hashtable ht = Spring_main_table
        
        call UnitAddAbility(u, SPRING_ABILITIES_UNIT_INDEXER)
        call UnitMakeAbilityPermanent(u, true, SPRING_ABILITIES_UNIT_INDEXER)
        call SaveBoolean(ht, uid, KEY_UNIT_REINCARNATING, false)
        
        static if SPRING_DAMAGE_DETECT then
            call TriggerRegisterUnitEvent(DDCurrentTrigger, u, EVENT_UNIT_DAMAGED)
        endif
		
		static if SPRING_UNIT_INDEXER then
			call Spring_createUnitId(u)
		endif

        if HaveSavedInteger(ht, KEY_UNIT_TYPE_ID_FILTERS, typeId) then
            set arr = LoadInteger(ht, KEY_UNIT_TYPE_ID_FILTERS, typeId)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, arr, i), 1)
                set i = i - 1
            endloop
            set valid = true
        endif
        
        if HaveSavedInteger(ht, KEY_UNIT_NAME_FILTERS, namehash) then
            set arr = LoadInteger(ht, KEY_UNIT_NAME_FILTERS, namehash)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, arr, i), 1)
                set i = i - 1
            endloop
            set valid = true
        endif

        if HaveSavedInteger(ht, KEY_NULL_FILTERS, 0) then
            set arr = LoadInteger(ht, KEY_NULL_FILTERS, 0)
            set i = LoadInteger(ht, arr, KEY_ARRAY_SIZE) - 1
            loop
                exitwhen 0 > i
                call ForceEnumPlayersCounted(f, LoadBooleanExprHandle(ht, arr, i), 1)
                set i = i - 1
            endloop
            set valid = true
        endif
        
        if valid then
            call MapEnterEventHandler()
        endif
        
        set ht = null
        set u = null
        set f = null
        return true
    endfunction

    
    // ----------------------------

    private function init takes nothing returns nothing
        local trigger t
        local integer i 
        local region r 
        local boolexpr autoCreateConditions
        local player p
        local group g
        local boolexpr UnitFilter = Condition(function Spring_UnitFilter)
        
        // create damage detect group and trigger
        static if SPRING_DAMAGE_DETECT then
            // the damage event will be added on unit entry.
            set DDCurrentTrigger = CreateTrigger()
            call TriggerAddCondition(DDCurrentTrigger, Condition (function DamageEventHandler))
            set DDSwapGroup = CreateGroup()      
            // if a DD trigger execute thread lasts more than 666 seconds, prepare to meet the Satan of wc3.
            // for the faint of heart comment it out just to be safe (will cause minor leaks though)
            call TimerStart(CreateTimer(),  666.7, true, function DDSwapTrigger)
        endif
        
        set autoCreateConditions = UnitFilter
        set autoCreateConditions = And(autoCreateConditions, Condition(function InitUnitStructsOnEnter))

        // enter map handler for all units placed in editor
        set g = CreateGroup()
        set t = CreateTrigger()
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYER_SLOTS
            set p = Player(i)
            call GroupEnumUnitsOfPlayer(g, p, autoCreateConditions)
            call SetPlayerAbilityAvailable(p, SPRING_ABILITIES_UNIT_INDEXER, false)
            set i = i + 1
        endloop
            
        // enter map handler
        set t = CreateTrigger()
        set r = CreateRegion()
        set MAP_RECT = GetWorldBounds()
        call RegionAddRect(r, GetWorldBounds())
        call TriggerRegisterEnterRegion(t, r , autoCreateConditions)

        // unit leaves handler
        set t = CreateTrigger()
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYER_SLOTS
            set p = Player(i)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, UnitFilter)
            set i = i + 1
        endloop
        call TriggerRegisterPlayerUnitEvent(t, Player(12), EVENT_PLAYER_UNIT_DEATH, UnitFilter)
        call TriggerAddCondition(t, Condition(function DestroyUnitStructsOnExit))
        
        // spell event handler
        set t = CreateTrigger()
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYER_SLOTS
            set p = Player(i)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CHANNEL, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CAST, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_ENDCAST, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_FINISH, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_HERO_SKILL, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, UnitFilter)
            set i = i + 1
        endloop
        call TriggerAddCondition(t, Condition (function SpellEventHandler))
        
        // state change event handler
        set t = CreateTrigger()
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYER_SLOTS
            set p = Player(i)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_HERO_LEVEL, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_HERO_REVIVE_START, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_HERO_REVIVE_FINISH, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_DEATH, UnitFilter)
            call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_CHANGE_OWNER, UnitFilter)
            set i = i + 1
        endloop
        call TriggerAddCondition(t, function StateChangeEventHandler)

        static if SPRING_ATTACK_DETECT then
            set t = CreateTrigger()
            set i = 0
            loop
                exitwhen i >= bj_MAX_PLAYER_SLOTS
                set p = Player(i)
                // no unit filter in here because in order to detect an attack, you must detect an attacked
                call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ATTACKED, null)
                set i = i + 1
            endloop
            call TriggerAddCondition(t, Condition (function AttackEventEventHandler))
        endif
 
        set r = null
        set t = null
        set g = null
        set r = null
        set p = null
    endfunction
    private function Spring_Execute_Inits takes nothing returns nothing
        local integer i = 0
        local integer max = initFunctions_N - 1
		local force f = SPRING_DUMMY_FORCE
        local string quote = "\""          //"
		
		//! textmacro add_event_to_array takes E
			set SPRING_ALL_EVENT_ARRAY[i] = $E$
			set i = i + 1
		//! endtextmacro

        //! runtextmacro add_event_to_array("EVENT_CHANGE_OWNER")
        //! runtextmacro add_event_to_array("EVENT_LEVEL")
        //! runtextmacro add_event_to_array("EVENT_UNIT_DIE")
        
        //! runtextmacro add_event_to_array("EVENT_SPELL_LEARN")
        //! runtextmacro add_event_to_array("EVENT_SPELL_EFFECT")
        //! runtextmacro add_event_to_array("EVENT_SPELL_CHANNEL")
        //! runtextmacro add_event_to_array("EVENT_SPELL_CAST")
        //! runtextmacro add_event_to_array("EVENT_SPELL_ENDCAST")
        //! runtextmacro add_event_to_array("EVENT_SPELL_FINISH")
        
        //! runtextmacro add_event_to_array("EVENT_ISSUED_ORDER")
        //! runtextmacro add_event_to_array("EVENT_ISSUED_POINT_ORDER")
        //! runtextmacro add_event_to_array("EVENT_ISSUED_TARGET_ORDER")
        //! runtextmacro add_event_to_array("EVENT_ISSUED_UNIT_ORDER")
        
        //! runtextmacro add_event_to_array("EVENT_ATTACKED")
        //! runtextmacro add_event_to_array("EVENT_ATTACK")
        
        //! runtextmacro add_event_to_array("EVENT_DAMAGED")
        //! runtextmacro add_event_to_array("EVENT_DAMAGE")
        
        //! runtextmacro add_event_to_array("EVENT_REINCARNATION_START")
        //! runtextmacro add_event_to_array("EVENT_REINCARNATION_FINISH")
        
        //! runtextmacro add_event_to_array("EVENT_RESSURECT")
        //! runtextmacro add_event_to_array("EVENT_ENTER_MAP")
        
        set SPRING_ALL_EVENT_ARRAY_LENGTH = i
		
		if StringHash(GetObjectName('AIDX')) == StringHash("Default string") then
			call BJDebugMsg("SpringConfig:")
			call BJDebugMsg("'AIDX' is missing, expect further failures.")
			call BJDebugMsg(" ")
			call BJDebugMsg("paste the line below to the editor and run once, then delete it.")
			call BJDebugMsg("or manually create a defend based dummy ability with AIDX as its object id")
			call BJDebugMsg(" ")
			call BJDebugMsg("//! external ObjectMerger w3a Adef AIDX anam " + quote + "Spring Internal" + quote + " aart " + quote + quote + " arac 0")
		endif
		
        // whoring single namespace by using SpringHandles library to create
        // handles that share the same key range as all wc3 handles, thus everything
        // is unique and safe to be used within a single hashtable without worrying about
        // overrides
        set KEY_ARRAY_SIZE = CreateHandle()
        set KEY_INSTANCE_TO_INSTANCE_ID = CreateHandle()
        set KEY_UNIT_REINCARNATING =  CreateHandle()
        set KEY_UNIT_DEAD = CreateHandle()
        set KEY_UNIT_TYPE_ID_FILTERS = CreateHandle()
        set KEY_UNIT_NAME_FILTERS = CreateHandle()
        set KEY_NULL_FILTERS = CreateHandle()
        set KEY_INSTANCE_TO_INSTANCE_ENABLED = CreateHandle()
        set KEY_INSTANCE_TO_INSTANCE_OBJECT_ID = CreateHandle()

		set i = 0
        loop
            exitwhen i > max
            call ForceEnumPlayersCounted(f, initFunctions[i], 1)
            set initFunctions[i] = null
            set i = i + 1
        endloop
        set f = null
        call init()
    endfunction
    private function INIT takes nothing returns nothing
        set Spring_Timer_1 = CreateTimer()
        call TimerStart(Spring_Timer_1,0.01, false, function Spring_Execute_Inits)
    endfunction
endlibrary